feat(cli): layered DATABASE_URL resolution for db / schema commands#377
feat(cli): layered DATABASE_URL resolution for db / schema commands#377
Conversation
🦋 Changeset detectedLatest commit: cb31fb3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an AsyncLocalStorage-backed resolver and CLI Changes
Sequence DiagramsequenceDiagram
participant User
participant CLI
participant Resolver
participant Env
participant Supabase
participant Prompt
participant Config
User->>CLI: run db/schema command (maybe `--database-url`)
CLI->>Resolver: withResolverContext(resolveDatabaseUrl(opts))
alt databaseUrlFlag present
Resolver->>Resolver: validate flag URL
Resolver-->>CLI: return URL (source: flag)
else process.env.DATABASE_URL present
Resolver->>Env: read DATABASE_URL
Resolver-->>CLI: return URL (source: env)
else supabase allowed/detected
Resolver->>Supabase: run `supabase status --output env` (or fallback)
alt DB_URL found
Resolver-->>CLI: return URL (source: supabase)
else no DB_URL / error
alt interactive (TTY && !CI)
Resolver->>Prompt: ask for URL (show dotenv tip)
Prompt-->>Resolver: url / cancel
alt url provided
Resolver-->>CLI: return URL (source: prompt)
else canceled
Resolver-->>CLI: exit(0)
end
else
Resolver-->>CLI: exit(1)
end
end
end
CLI->>Config: load stash.config.ts inside withResolverContext
Config->>Resolver: config may call resolveDatabaseUrl() and use resolved URL
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/cli/src/commands/db/test-connection.ts (1)
48-54:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUpdate the failure hint to include the new URL sources.
After this change, connection failures can come from
--database-url, the env var, or Supabase discovery. Pointing users only atstash.config.ts/.envis now misleading.💡 Suggested fix
- p.log.info('Check your databaseUrl in stash.config.ts or .env file.') + p.log.info( + 'Check the resolved DATABASE_URL source (--database-url, .env, or Supabase discovery).', + )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/commands/db/test-connection.ts` around lines 48 - 54, Update the failure hint printed in test-connection.ts so it reflects all possible URL sources instead of only stash.config.ts/.env: modify the p.log.info call in the error branch (the block that constructs message and calls p.log.error) to mention --database-url, the environment variable, and Supabase discovery as places to check; locate the error-handling block that defines message and calls p.log.error / p.log.info and change the info text accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli/src/config/database-url.ts`:
- Around line 160-161: The CI detection currently only checks process.env.CI ===
'true', causing CI values like '1' or other truthy strings to be missed; update
the isCi computation (used alongside isInteractive) to treat common truthy CI
values case-insensitively (e.g., '1', 'true', 'yes') or simply check for a
non-empty process.env.CI value (e.g., Boolean(process.env.CI) or a regex match)
so that isInteractive (which uses process.stdin.isTTY && !isCi) behaves
correctly in CI environments.
---
Outside diff comments:
In `@packages/cli/src/commands/db/test-connection.ts`:
- Around line 48-54: Update the failure hint printed in test-connection.ts so it
reflects all possible URL sources instead of only stash.config.ts/.env: modify
the p.log.info call in the error branch (the block that constructs message and
calls p.log.error) to mention --database-url, the environment variable, and
Supabase discovery as places to check; locate the error-handling block that
defines message and calls p.log.error / p.log.info and change the info text
accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7896bf05-8179-4618-9876-ae1f3978a98b
📒 Files selected for processing (12)
packages/cli/src/__tests__/database-url.test.tspackages/cli/src/bin/stash.tspackages/cli/src/commands/db/install.tspackages/cli/src/commands/db/push.tspackages/cli/src/commands/db/status.tspackages/cli/src/commands/db/test-connection.tspackages/cli/src/commands/db/upgrade.tspackages/cli/src/commands/db/validate.tspackages/cli/src/commands/schema/build.tspackages/cli/src/config/database-url.tspackages/cli/src/messages.tspackages/cli/tests/e2e/database-url.e2e.test.ts
There was a problem hiding this comment.
Pull request overview
Adds a layered DATABASE_URL resolver to improve onboarding for DB-touching CLI commands by supporting multiple sources (flag/env/Supabase/prompt) before stash.config.ts is loaded, plus tests to cover the new resolution paths.
Changes:
- Introduce
resolveDatabaseUrl()and wire it into DB/schema commands, including a new--database-urlflag. - Add user-facing message handles for resolver output and failure modes.
- Add unit + E2E coverage for resolver behavior (flag + CI guard).
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/cli/src/config/database-url.ts | New layered resolver for DATABASE_URL, including Supabase and prompt fallbacks. |
| packages/cli/src/bin/stash.ts | Plumbs --database-url into db/schema commands and updates help text. |
| packages/cli/src/messages.ts | Adds message handles for resolver logging/prompts/errors. |
| packages/cli/src/commands/db/install.ts | Runs resolver before config load; accepts databaseUrl option. |
| packages/cli/src/commands/db/upgrade.ts | Runs resolver before config load; accepts databaseUrl option. |
| packages/cli/src/commands/db/validate.ts | Runs resolver before config load; accepts databaseUrl option. |
| packages/cli/src/commands/db/status.ts | Runs resolver before config load; accepts databaseUrl option. |
| packages/cli/src/commands/db/test-connection.ts | Runs resolver before config load; accepts databaseUrl option. |
| packages/cli/src/commands/db/push.ts | Adds databaseUrl option and calls resolver before config load. |
| packages/cli/src/commands/schema/build.ts | Runs resolver before config load; accepts databaseUrl option. |
| packages/cli/src/tests/database-url.test.ts | New unit tests for resolver source ordering and guards. |
| packages/cli/tests/e2e/database-url.e2e.test.ts | New E2E tests validating flag path + CI fail-fast behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Onboarding users running `stash db install` (or any of the six other
DB-touching commands) without an exported `DATABASE_URL` would hit a
cryptic "Invalid stash.config.ts: databaseUrl received undefined"
error from Zod. The CLI already loads `.env.local` / `.env`* via
dotenv, but had no story for `--database-url` flags, local Supabase,
or a pasted-once one-off value.
Adds `src/config/database-url.ts` exporting `resolveDatabaseUrl()` —
called at the top of every DB-touching command before
`loadStashConfig`. It walks sources in order:
1. `--database-url <url>` flag (explicit override)
2. `process.env.DATABASE_URL` (shell, mise, direnv, dotenv files)
3. `supabase status --output env` -> DB_URL, gated on `--supabase`
or a `supabase/config.toml` in cwd
4. Interactive `p.text` prompt (skipped under `CI=true` or
non-TTY stdin)
5. Hard fail with a source-naming error message
The resolved URL is mutated into `process.env.DATABASE_URL` (only
when env was unset/empty, or the source was an explicit flag) so the
existing scaffolded `process.env.DATABASE_URL!` reference in
`stash.config.ts` resolves transparently. The connection string is
never written to disk — `stash.config.ts` keeps its declarative env
reference. The source label is logged (`Using DATABASE_URL from
--database-url flag` / `from supabase status` / `from prompt`) but
the URL itself is never echoed.
Wired through `bin/stash.ts` argv (the parser already supports
`--key value`), HELP text updated. All seven DB-touching commands
(`db install`, `db push`, `db upgrade`, `db status`, `db validate`,
`db test-connection`, `schema build`) accept `--database-url` and
call the resolver before `loadStashConfig`.
After a prompt-sourced run, a `p.note` nudges the user to set
DATABASE_URL in the existing dotenv file (or `.env` if none exists)
so they don't get re-prompted next time.
Tests:
- 13 unit tests (`__tests__/database-url.test.ts`) covering each
source, malformed flag, empty-string-env fallthrough, supabase
fallthrough on missing binary / no-DB_URL output / no project,
prompt-cancel, CI guard, non-TTY guard, hint-file detection.
- 2 E2E tests (`tests/e2e/database-url.e2e.test.ts`) driving the
built binary through the pty harness: `--database-url` flag
surfaces the source label and proceeds to (failing) connection,
CI=true with no creds exits 1 with the CI message.
`init`/build-schema is intentionally NOT wired through the resolver:
it reads `process.env.DATABASE_URL` for provider hints pre-config and
should not trigger the prompt flow.
Note: stacks on top of #375 (the jiti default-export unwrap fix). The
resolver populates env, but `loadStashConfig` still needs the jiti
fix to read the resulting config correctly. Both PRs together
deliver the full onboarding experience.
d7bd47c to
35d9a2c
Compare
…utation
The previous design had the resolver mutate `process.env.DATABASE_URL`
in-flight before `loadStashConfig`, with the scaffolded
`stash.config.ts` referencing `process.env.DATABASE_URL!`. Two issues:
1. Reading the config file made it look like the URL only ever came
from env, even when the user had passed `--database-url` or hit
the supabase-status fallback. Misleading.
2. Mutating `process.env` in-flight is a global side-effect.
New shape — the scaffolded config calls the resolver directly:
import { defineConfig, resolveDatabaseUrl } from '@cipherstash/cli'
export default defineConfig({
databaseUrl: await resolveDatabaseUrl(),
})
Reading the file tells you exactly how the URL is resolved. No env
side-effects.
Implementation:
- `withResolverContext({ databaseUrlFlag, supabase }, fn)` runs `fn`
inside an `AsyncLocalStorage` scope. Any `resolveDatabaseUrl()`
descendant in the async-flow reads the options via `als.getStore()`.
Each `loadStashConfig` call gets its own scope, so concurrent loads
don't step on each other (verified by a dedicated unit test).
- The ALS instance is stashed on `globalThis` under a
`Symbol.for`-keyed slot. Necessary because tsup ships the library
(`dist/index.js`) and the binary (`dist/bin/stash.js`) as separate
bundles, each with its own copy of `database-url.ts`. A bare
module-level ALS would produce two independent stores: the CLI sets
context on the binary's instance, the user's config (loaded via
jiti from inside the binary process) reads from the library's. The
globalThis rendezvous gives both bundles a single shared instance.
- `resolveDatabaseUrl` returns `string` (was previously
`{ url, source }`) so the scaffolded `databaseUrl: await
resolveDatabaseUrl()` is direct. The source label is logged
internally; not part of the return contract.
- All seven DB-touching commands stop calling `resolveDatabaseUrl`
themselves. Instead they pass `{ databaseUrlFlag, supabase }` to
`loadStashConfig`, which threads them into the ALS scope around the
jiti-import.
- Prompt now opens with a `p.note` listing the alternative paths
(--database-url flag, env var, .env file) so users aren't stuck in
an interactive flow when a flag would do.
- `process.env.DATABASE_URL` is never mutated by this code.
Tests:
- Unit suite (16 tests, was 13) updated for the new return type and
no-env-mutation invariant. Adds an explicit concurrency test:
two `withResolverContext` scopes started in parallel each see their
own flag value. This is the regression net for the ALS choice.
- E2E suite updated: the test fixture config imports
`resolveDatabaseUrl` from an absolute path to `dist/index.js` (the
tmp dir has no node_modules). Real users get a clean
`import { resolveDatabaseUrl } from '@cipherstash/cli'`.
No legacy-config handling — this hasn't shipped yet, fresh installs
get the new template directly.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli/src/__tests__/database-url.test.ts`:
- Around line 63-81: The test teardown uses vi.clearAllMocks() which does not
restore spied globals like process.exit, causing spy leakage; replace
vi.clearAllMocks() with vi.restoreAllMocks() in the afterEach block so spies
created in tests (e.g., process.exit spies referenced around the tests that
create spies at lines near 105,127,159,176,190,234,249,264) are fully restored
to their original implementations and won't affect later tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b887a2f8-bbc7-440e-83de-257f2c5db27a
📒 Files selected for processing (14)
packages/cli/src/__tests__/database-url.test.tspackages/cli/src/commands/db/config-scaffold.tspackages/cli/src/commands/db/install.tspackages/cli/src/commands/db/push.tspackages/cli/src/commands/db/status.tspackages/cli/src/commands/db/test-connection.tspackages/cli/src/commands/db/upgrade.tspackages/cli/src/commands/db/validate.tspackages/cli/src/commands/schema/build.tspackages/cli/src/config/database-url.tspackages/cli/src/config/index.tspackages/cli/src/index.tspackages/cli/src/messages.tspackages/cli/tests/e2e/database-url.e2e.test.ts
✅ Files skipped from review due to trivial changes (1)
- packages/cli/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/cli/src/commands/db/push.ts
- packages/cli/src/commands/db/validate.ts
- packages/cli/src/messages.ts
…v file Two follow-ups from manual testing: 1. The prompt tip referred to ".env file" generically, even when the project had a `.env.local` (the post-resolution hint already matched the detected file, but the up-front tip didn't). 2. `db test-connection`'s connection-failure handler suggested "Check your databaseUrl in stash.config.ts or .env file". With the new design `stash.config.ts` doesn't carry the URL — it calls `resolveDatabaseUrl()` — so pointing users there is actively misleading. Both messages now go through `detectDotenvFile(cwd)` (newly exported) and the message factories `messages.db.urlPromptTip(file)` and `messages.db.urlConnectionFailedHint(file)`. The user sees the exact dotenv file they have in their project, plus the other ways they can specify the URL (--database-url flag, env var). Adds a unit test covering the dynamic-tip behaviour for both the detected-file and default-`.env` cases.
Two findings: 1. CI detection in resolveDatabaseUrl was only matching `CI === 'true'` exactly. Some CI providers set `CI=1` or use mixed case. Broadened to `/^(1|true)$/i` against trimmed input, plus a parameterized test covering 'true' / 'TRUE' / '1' / ' true '. 2. Test afterEach used vi.clearAllMocks(), which resets mock-call history but does NOT restore spies on global objects. The process.exit spies created in individual tests would leak across tests (each test re-spied so the symptom was masked, but the leak was real). Switched to vi.restoreAllMocks(), which calls mockRestore() on all spies and reverts them to their originals.
The connection-failure hint update (commit 8248200) routed testConnectionCommand's `p.log.info` through `messages.db.urlConnectionFailedHint(detectDotenvFile())` so users see flag / env / dotenv-file as fixable sources, not the now-irrelevant `stash.config.ts`. Behaviour was manually verified but no automated test guarded it. Extends the existing `--database-url` flag E2E case (where the connection deliberately fails against a bogus host:port) with two assertions: - the failure output contains the new hint, with the dotenv file detected from the tmp dir (defaults to `.env` since the dir has no dotenv files) - the OLD misleading "stash.config.ts or .env file" wording is absent Same spawn, two extra `expect()` calls. Closes the gap surfaced by the manual-vs-automated audit on PR #377.
Adds step 7 to the "Adding Features Safely" checklist explicitly requiring a changeset when a change affects a published package's public behaviour. Includes the exact file format, points at the changeset-bot warning, and lists the carve-outs (test-only PRs, internal refactors, tooling) so contributors don't add empty changesets when one isn't needed. Without this, agent-driven PRs were silently shipping changes that didn't surface in CHANGELOG.md or trigger version bumps.
auxesis
left a comment
There was a problem hiding this comment.
Really solid work that you absolutely love to see.
This config lookup mechanism is really robust.
Have left some comments about hard coded npx that need to be fixed up before merge. Some are introduced in this PR, others are already there. #379 should make this much easier to fix.
Lindsay flagged on PR #377 that the DB commands hard-code `npx @cipherstash/cli ...` in their `p.intro` and warning messages — some lines I touched in this PR, others pre-existing. #379 already landed `runnerCommand(pm, ref)` + `detectPackageManager()` for exactly this; sweeping the six DB commands to use it now. A user who runs `bunx @cipherstash/cli db status` now sees `bunx @cipherstash/cli db status` as the intro banner (was: always `npx ...` regardless of how they invoked the CLI), and warnings like "Run `bunx @cipherstash/cli db install` to install it" or "run `pnpm dlx @cipherstash/cli db push` to create it" follow the same rule. Files touched (all already in this PR's diff for the resolver plumbing): - db/install.ts — intro - db/upgrade.ts — intro + "run db install" warning - db/push.ts — intro - db/status.ts — intro + 2 warnings (db install, db push) - db/validate.ts — intro - db/test-connection.ts — intro Out of scope: the help-banner usage examples in `bin/stash.ts` and `auth/index.ts`. Those are documentation listing all supported commands, not runtime "this is what I ran" labels — separate UX question whether `--help` should pick a single runner or list all four. The wizard reference in `db/install.ts:276` is a comment inside a generated SQL file's header; same separate concern.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/cli-database-url-resolution.md:
- Around line 9-12: The fenced code block showing "Error: Invalid
stash.config.ts" in .changeset/cli-database-url-resolution.md is missing a
language specifier causing MD040; update that fenced block by adding a language
identifier (e.g., "text") after the opening triple backticks so the block
becomes ```text and the linter rule fenced-code-language is satisfied.
In `@AGENTS.md`:
- Line 175: The fenced code block shown with triple backticks is missing a
language tag (causing MD040); update that fence to include a language identifier
(for example use "text") so the opening delimiter becomes ```text and the block
remains otherwise unchanged—look for the ``` fence in AGENTS.md and add the
language tag.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 300c785c-a07b-4769-b9a8-c3b411ee9167
📒 Files selected for processing (8)
.changeset/cli-database-url-resolution.mdAGENTS.mdpackages/cli/src/commands/db/install.tspackages/cli/src/commands/db/push.tspackages/cli/src/commands/db/status.tspackages/cli/src/commands/db/test-connection.tspackages/cli/src/commands/db/upgrade.tspackages/cli/src/commands/db/validate.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/cli/src/commands/db/validate.ts
- packages/cli/src/commands/db/push.ts
- packages/cli/src/commands/db/test-connection.ts
- packages/cli/src/commands/db/upgrade.ts
Follow-up to PR #377 addressing Lindsay's feedback that the HELP banners and the wizard reference in `db install`'s "Next steps" panel still hard-coded `npx`. #379 already added `runnerCommand(pm, ref)` + `detectPackageManager()`; this sweep applies it to the remaining places. User-visible: a user who runs `bunx @cipherstash/cli --help` now sees `bunx @cipherstash/cli` in the Usage line and Examples, and the wizard suggestion at the end of `db install` matches the user's runner. Same for `pnpm dlx`, `yarn dlx`, default `npx`. Touches: - `bin/stash.ts` — `HELP` template now interpolates a `STASH` constant (`runnerCommand(PM, '@cipherstash/cli')`) for the Usage line, the six Examples, and the `requireStack` hint. - `commands/auth/index.ts` — same pattern for the auth HELP, with a `STASH_AUTH` constant for the Usage line + two examples. - `commands/db/install.ts` — the wizard line in `printNextSteps` uses `runnerCommand(pm, '@cipherstash/wizard')`. Also brings the `p.intro('npx @cipherstash/cli db install')` line in line with the runner-aware pattern (was already pinned for the same reason in PR #377; this branch is off main, so it lands here too). Messages: `messages.cli.usagePrefix` and `messages.auth.usagePrefix` become stable leaders (`'Usage: '`) — the runner+package suffix is appended at render time. E2E assertions stay runner-agnostic because they `toContain('Usage: ')` rather than the full prefix. Out of scope (still): the migrate stub message in `messages.db.migrateNotImplemented` keeps its hard-coded `npx` form. The stub is a placeholder and the inconsistency only shows when someone runs a not-yet-implemented command — not worth plumbing for.
Follow-up to PR #377 addressing Lindsay's feedback that the HELP banners and the wizard reference in `db install`'s "Next steps" panel still hard-coded `npx`. #379 already added `runnerCommand(pm, ref)` + `detectPackageManager()`; this sweep applies it to the remaining places. User-visible: a user who runs `bunx @cipherstash/cli --help` now sees `bunx @cipherstash/cli` in the Usage line and Examples, and the wizard suggestion at the end of `db install` matches the user's runner. Same for `pnpm dlx`, `yarn dlx`, default `npx`. Touches: - `bin/stash.ts` — `HELP` template now interpolates a `STASH` constant (`runnerCommand(PM, '@cipherstash/cli')`) for the Usage line, the six Examples, and the `requireStack` hint. - `commands/auth/index.ts` — same pattern for the auth HELP, with a `STASH_AUTH` constant for the Usage line + two examples. - `commands/db/install.ts` — the wizard line in `printNextSteps` uses `runnerCommand(pm, '@cipherstash/wizard')`. Also brings the `p.intro('npx @cipherstash/cli db install')` line in line with the runner-aware pattern (was already pinned for the same reason in PR #377; this branch is off main, so it lands here too). Messages: `messages.cli.usagePrefix` and `messages.auth.usagePrefix` become stable leaders (`'Usage: '`) — the runner+package suffix is appended at render time. E2E assertions stay runner-agnostic because they `toContain('Usage: ')` rather than the full prefix. Out of scope (still): the migrate stub message in `messages.db.migrateNotImplemented` keeps its hard-coded `npx` form. The stub is a placeholder and the inconsistency only shows when someone runs a not-yet-implemented command — not worth plumbing for.
Closes #376. Stacks on top of #375 (jiti default-export unwrap fix) — both together deliver the full onboarding experience.
Problem
Running any DB-touching command without
DATABASE_URLexported in the shell or in a loaded.env*file fails with the cryptic Zod error:Users provide DB URLs through many channels:
--database-urlflag, shell exports,mise.toml,direnv,dotenv-cli, local Supabase, or simply "let me paste it once". The CLI handled only the dotenv path.Fix
The scaffolded
stash.config.tsnow calls the resolver directly:resolveDatabaseUrl()walks sources in order; first hit wins:--database-url <url>flag (explicit override)process.env.DATABASE_URL(shell, mise, direnv, dotenv files already loaded bybin/stash.ts)supabase status --output env→DB_URL, gated on--supabaseor asupabase/config.tomlin cwdp.textprompt — skipped underCI=trueor non-TTY stdinCLI flag values are threaded into the user's config evaluation via an
AsyncLocalStoragescope (withResolverContext) wrapped around the jiti import inloadStashConfig.process.env.DATABASE_URLis never mutated by this code. The connection string is never written to disk —stash.config.tsreferences the resolver, not a literal. ConcurrentloadStashConfigcalls each get their own ALS context (regression test included).The source is logged (
Using DATABASE_URL from --database-url flag/from supabase status/from prompt); the URL itself is never echoed to stdout.After a prompt-sourced run, a
p.notenudges the user to setDATABASE_URLin their existing dotenv file (detected from cwd, defaults to.env) so they don't get re-prompted next time.Surface
--database-url <url>accepted on all seven DB-touching commands:db install,db push,db upgrade,db status,db validate,db test-connection,schema build. HELP text updated.init/build-schema is not wired through the resolver — it readsprocess.env.DATABASE_URLfor provider hints pre-config and should not trigger the prompt flow.Test plan
pnpm --filter @cipherstash/cli test— 117 unit tests pass (+ new__tests__/database-url.test.tscovering each source, CI guard, concurrent ALS isolation, dotenv-file detection)pnpm --filter @cipherstash/cli test:e2e— 10 E2E tests pass (+ newtests/e2e/database-url.e2e.test.ts: flag-source surfaces the label, CI guard exits 1 with the CI message)biome checkclean on changed filespnpm --filter @cipherstash/cli buildcleanManual verification
Verified each resolution path locally against the built
dist/bin/stash.jswith a tempstash.config.tsthat callsawait resolveDatabaseUrl():db test-connection --database-url <url>logsUsing DATABASE_URL from --database-url flag, then attempts connection. URL never echoed in output.DATABASE_URL=<url> db test-connectionruns silently (no source label, env is the default).CI=true db test-connection(no env, no flag) exits 1 with the CI-specific message; never prompts.db test-connection(no env, no flag) shows the alternatives tip viap.note, then the URL prompt..env.localpresent, the post-prompt nudge saysSet DATABASE_URL in .env.local…; with no dotenv file, defaults to.env. Tip note also matches the detected file.--database-url not-a-urlexits 1 withInvalid --database-url.Cancelled.supabase init && supabase startproject, the resolver shells out tosupabase status --output env, parsesDB_URL, logsUsing DATABASE_URL from supabase status, and connects successfully.--database-url/ env / the detected dotenv file, not atstash.config.ts.process.env.DATABASE_URLis unchanged after every scenario — the resolver does not mutate the env.from --database-url flag,from supabase status,from prompt).Note on coverage
The unit tests use
vi.mockto stubnode:child_process,@clack/prompts, anddetectSupabaseProject, so each source path is exercised in isolation. The E2E tests run the builtdist/bin/stash.jsthrough the pty harness against a tempstash.config.tsfor the flag and CI-guard cases. The non-TTY path is covered by the unit suite (Object.defineProperty(process.stdin, 'isTTY', false)) since the pty harness always provides a TTY.The ALS instance is parked on
globalThisunder aSymbol.forkey — necessary because tsup ships the library (dist/index.js, what user configs import) and the binary (dist/bin/stash.js, what npx runs) as separate bundles, each with its own copy ofdatabase-url.ts. TheSymbol.forrendezvous gives both bundles a single shared ALS for the lifetime of the process.Summary by CodeRabbit
New Features
Improvements
Tests
Docs